package com.qianfeng.smsplatform.apigateway.filters;

//
//                            _ooOoo_  
//                           o8888888o  
//                           88" . "88  
//                           (| -_- |)  
//                            O\ = /O  
//                        ____/`---'\____  
//                      .   ' \\| |// `.  
//                       / \\||| : |||// \  
//                     / _||||| -:- |||||- \  
//                       | | \\\ - /// | |  
//                     | \_| ''\---/'' | |  
//                      \ .-\__ `-` ___/-. /  
//                   ___`. .' /--.--\ `. . __  
//                ."" '< `.___\_<|>_/___.' >'"".  
//               | | : `- \`.;`\ _ /`;.`/ - ` : | |  
//                 \ \ `-. \_ __\ /__ _/ .-` / /  
//         ======`-.____`-.___\_____/___.-`____.-'======  
//                            `=---='  
//  
//         .............................................  
//                  佛祖镇楼                  BUG辟易  
//          佛曰:  
//                  写字楼里写字间，写字间里程序员；  
//                  程序人员写程序，又拿程序换酒钱。  
//                  酒醒只在网上坐，酒醉还来网下眠；  
//                  酒醉酒醒日复日，网上网下年复年。  
//                  但愿老死电脑间，不愿鞠躬老板前；  
//                  奔驰宝马贵者趣，公交自行程序员。  
//                  别人笑我忒疯癫，我笑自己命太贱；  
//  


import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.qianfeng.smsplatform.apigateway.cache.GrayReleaseCache;
import com.qianfeng.smsplatform.apigateway.pojo.TGrayReleaseConfig;
import io.jmnarloch.spring.cloud.ribbon.support.RibbonFilterContextHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by Jackiechan on 2022/3/18/下午3:29
 *
 * @author Jackiechan
 * @version 1.0
 * @since 1.0
 */
@Component
public class GrayReleaseFilter extends ZuulFilter {
    //临时缓存服务灰度的规则,这样不用每次都计算，以服务的标识作为key
    Map<String, List<String>> serivceIdUrlForward = new ConcurrentHashMap<>();



    private GrayReleaseCache grayReleaseCache;


    @Autowired
    public void setGrayReleaseCache(GrayReleaseCache grayReleaseCache) {
        this.grayReleaseCache = grayReleaseCache;
    }

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 200;
    }

    @Override
    public boolean shouldFilter() {
        boolean isNeed = false;
        //前面的过滤器可能会拦截请求,这里需要对请求进行判断,如果拦截了,就不执行下面的run,我们还需要判断是不是开起了灰度发布，没有开启的也不走
        RequestContext currentContext = RequestContext.getCurrentContext();
        boolean b = currentContext.sendZuulResponse();//看看前面有没有拦截
        if (!b) {
            return b;//如果拦截了直接返回false
        }

        String method = currentContext.getRequest().getParameter("method");//获取到当前请求的服务的标识
        List<String> list = serivceIdUrlForward.get(method);
        if (list == null || list.size() == 0) {

            //先获取到请求的地址，然后去找我们本地缓存的灰度发布的规则，然后再看看有没有当前对应的规则
            String serviceId = (String) currentContext.get(FilterConstants.SERVICE_ID_KEY);
            String url = (String) currentContext.get(FilterConstants.REQUEST_URI_KEY);

            if (StringUtils.isEmpty(serviceId) || StringUtils.isEmpty(url)) {
                return false;
            }
            //按照当前的服务和请求地址找有没有灰度发布,经过我们分析，我们现在拿到了请服务和url  是无法直接找到对应的灰度发布的，因为请求的地址可能是rest风格的，有path参数，但是我们的定义的时候是针对所有地址的
            //当前服务下的所有的灰度发布,我们可以在内部继续细分化的区分这个地址对不对
            List<TGrayReleaseConfig> grayReleaseConfigList = grayReleaseCache.get(serviceId);

            List<TGrayReleaseConfig> currentConfigList = null;//当前地址需要的灰度发布
            if (grayReleaseConfigList == null || grayReleaseConfigList.size() == 0) {
                //当前服务没有灰度数据，因为整个服务都没有，那么肯定具体的地址也没有
                return false;
            }

            labe:for (TGrayReleaseConfig config : grayReleaseConfigList) {
                //遍历所有的数据，看看哪个和当前的地址是有关系的.
                String path = config.getPath();//拿到的是灰度发布的路径
//
//            if (path.equalsIgnoreCase(url)) {
//                //代表访问的路径和当前的路径是一样的，代表需要开启灰度，但是前面的程序可能拦截了操作，所以我们把之前的判断扔到前面
//                //return true;
//                isNeed = true;
//            }

                //但是路径可能不一样，一个是定义的路径化参数， 一个是实际的路径，我们需要去判断是不是一个地址
                //我们以/裁剪两个地址， 如果长度是一样的，并且每个位置上的数据是一样的，如果有一个是{}的参数，则当前位置直接是一样的，如果所有的位置也是一样的，那就是一样的地址

                String[]  urls= url.split("/");//请求的地址
                String[] paths = path.split("/");//这个是我们要求的
                if (urls.length != paths.length) {
                    //  return false;//两个长度不一样,肯定不是一个地址
                    continue;
                }


                for (int i = 0; i < urls.length; i++) {
                    String currentUrl = urls[i];
                    String currentPath = paths[i];
                    if (!currentPath.contains("{") && !currentPath.equalsIgnoreCase(currentUrl)) {
                        //  return false;//当前位置的内容不包含{}路径参数并且两个数据还不一样，就不是同一个地址
                        continue labe;
                    }


                }
                if (currentConfigList == null) {
                    currentConfigList = new ArrayList<>();
                    currentContext.put("grayreleaseconfig", currentConfigList);//保存下当前服务的灰度规则，在run方法可以直接获取
                }
                currentConfigList.add(config);//把当前的规则保存起来
                isNeed = true;
            }
        }else {
            return true;//如果灰度里面有数据，就一定返回true
        }


        return isNeed;
    }

    @Override
    public Object run() throws ZuulException {
        //根据当前请求的的服务的,找到对应的灰度发布的规则，灰度发布的规则保存在什么地方?灰度发布的规则是不是经常变化?不怎么变化,我们可以保存到缓存中
        //需要找到当前请求的地址对应的灰度发布的规则,找的方式其实在前面应该就用过了.理论上我们要先判断当前地址是不是要用灰度发布，其实就是在找有没有规则
        //如果没有规则就代表不需要灰度，有的话我们可以直接先取出来，实际上在上面的是够需要开启的地方就可以获取到，所以我们经过分析先回去判断是不是开启灰度.
        //1 获取当前地址的灰度规则,然后按照规则进行判断
            //1.1 我们的规则会有很多, 比如x次访问a, y次访问b  n次访问c,所以我们需要获取到当前访问什么,并且下次要在当前的基础上进行继续判断
            //1.2 最好的办法就是我们把规则缓存起来
        RequestContext currentContext = RequestContext.getCurrentContext();
        String method = currentContext.getRequest().getParameter("method");//获取到当前请求的服务的标识

//        List<String> list = serivceIdUrlForward.get(tGrayReleaseConfig.getServiceId() + tGrayReleaseConfig.getPath());//以当前的请求的服务和地址作为key从缓存中获取数据， 一开是肯定是空的
        List<String> list = serivceIdUrlForward.get(method);//以当前的请求的服务和地址作为key从缓存中获取数据， 一开是肯定是空的
        if (list == null || list.size() == 0) {
            //获取到上面初始化的数据
            List<TGrayReleaseConfig> grayReleaseConfigList  = (List<TGrayReleaseConfig>) currentContext.get("grayreleaseconfig");//获取到相关的规则
            //初始化数据
            list = new ArrayList<>();
            serivceIdUrlForward.put(method, list);
            //将集合中所有的规则放到当前新创建的集合中
            for (TGrayReleaseConfig grayReleaseConfig : grayReleaseConfigList) {
                String forward = grayReleaseConfig.getForward();//我们要转发的服务的标识
                Integer percent = grayReleaseConfig.getPercent();//获取到权重
                for (Integer i = 0; i < percent; i++) {
                    list.add(forward);//将权重数据写入到临时的缓存中
                }
            }
            Collections.shuffle(list);//将权重打乱，这样就相当于随机从里面获取一个值
        }

        String forwardString = list.remove(0);//每次从头部拿出一个标识，来进行访问

        RibbonFilterContextHolder.getCurrentContext().add("forward", forwardString);//转发到指定标识的服务上面

        return null;
    }
}
